"use client"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler"; import { useSectionOverlay } from "@/components/Componentes/section-overlay-host"; import Button from "@/components/Componentes/button"; import DataErrorState from "@/components/Componentes/data-error-state"; import { FixToTheEnd } from "@/components/Componentes/fix-to-the-end"; import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import NavigationButton from "@/components/Componentes/navigation-button"; import { PageBackground } from "@/components/Componentes/page-background"; import { hasQuestionAnswerValue, QuestionAnswersProvider, useQuestionAnswers, } from "@/components/Componentes/question-answer-storage"; import QuestionExitNavigationButton from "@/components/Componentes/question-exit-navigation-button"; import QuestionRenderer from "@/components/Componentes/question-renderer"; import { parseValue as parseBirthplaceValue } from "@/components/Componentes/question-birthplace"; import QuestionSectionFlow from "@/components/Componentes/question-section-flow"; import StickyHeader from "@/components/Componentes/sticky-header"; import TestIntroPage from "@/components/Componentes/test-intro-page"; import TestQuestionsFlow, { type TestQuestion, } from "@/components/Componentes/test-questions-flow"; import { getGlasserQuestions, useGlasserQuestionsQuery, useSubmitGlasserAssessmentMutation, } from "@/hooks/marriage/use-glasser"; import { getCattellQuestions, useCattellQuestionsQuery, useSubmitCattellAssessmentMutation, } from "@/hooks/marriage/use-cattell"; import { useQueryClient } from "@tanstack/react-query"; import { marriageQueryKeys } from "@/hooks/marriage/query-keys"; import { useFormOverviewQuery, useFormSectionQuery, } from "@/hooks/marriage/use-form-schema"; import { convertOverviewToFrontendItems, mapBackendSectionToFrontend, type QuestionField, } from "@/lib/schema-adapter"; import { isQuestionVisible, isQuestionRequired } from "@/lib/conditional-rules"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { defaultLocale, type Locale } from "@/translations/config"; import { useI18n } from "@/translations/provider"; import { useCurrentProfileId } from "@/hooks/use-current-profile-id"; import { getScopedAssessmentDraftKey, readScopedAssessmentDraft, removeScopedAssessmentDraft, } from "@/lib/user-scoped-storage"; type QuestionDetailClientProps = { closeLabel: string; continueLabel: string; description: string; informationLabel: string; itemSlug: string; locale?: Locale; questionsListHref: string; title: string; onClose?: () => void; }; type StoredQuestionField = { label?: string; value?: unknown; type?: string; key?: string; }; type StoredAnswers = { fields?: StoredQuestionField[]; }; function getTestDraftStorageKey(slug: string, profileId: number | null) { if (!profileId) return null; return getScopedAssessmentDraftKey(profileId, slug); } function getQuestionStorageKey(slug: string, profileId: number | null) { if (!profileId) return null; return `marriage:user:${profileId}:sections:${slug}:completed`; } function QuestionFlowWrapper({ questions, itemSlug, continueLabel, questionsListHref, onExit, }: { questions: QuestionField[]; itemSlug: string; continueLabel: string; questionsListHref: string; onExit?: () => void; }) { const { getAnswerValue, answers } = useQuestionAnswers(); const { data: profile } = useMarriageProfileQuery(); const computedAge = useMemo(() => { if (typeof profile?.age === "number" && !isNaN(profile.age)) { return profile.age; } const dobAnswer = answers["personal_identity.date_of_birth"] || answers["personal_info.date_of_birth"] || answers["date_of_birth"] || Object.entries(answers).find( ([k]) => k.includes("date_of_birth") || k.includes("birth_date"), )?.[1]; const dobVal = typeof dobAnswer === "object" && dobAnswer !== null && "value" in dobAnswer ? dobAnswer.value : dobAnswer; if (dobVal && typeof dobVal === "string") { const parts = dobVal.replace(/\//g, "-").split("-"); if (parts[0] && !isNaN(Number(parts[0]))) { const y = Number(parts[0]); if (y >= 1300 && y <= 1500) { return Math.max(18, 1403 - y); } else if (y >= 1900 && y <= 2100) { const currentYear = new Date().getFullYear(); return Math.max(18, currentYear - y); } } } return undefined; }, [profile?.age, answers]); const userContext = useMemo( () => ({ gender: profile?.gender, age: computedAge, }), [profile?.gender, computedAge], ); const dynamicQuestions = useMemo(() => { return questions .filter((q) => isQuestionVisible(q, answers, userContext)) .map((q) => ({ ...q, required: isQuestionRequired(q, answers, userContext), })); }, [questions, answers, userContext]); const requiredCount = useMemo( () => dynamicQuestions.filter((q) => q.required).length, [dynamicQuestions], ); const dobQuestion = useMemo( () => dynamicQuestions.find( (question) => question.ui_config?.isDob === true || question.type === "date", ) || questions.find( (question) => question.ui_config?.isDob === true || question.type === "date", ), [dynamicQuestions, questions], ); return ( question.required ? [] : [index], )} questions={dynamicQuestions} > {dynamicQuestions.map((question, index) => { const answer = getAnswerValue(question); const hasAnswer = hasQuestionAnswerValue(answer ?? null); let isAnswered = hasAnswer; if (hasAnswer) { const isEmailQuestion = question.type === "email" || question.validation?.format === "email"; if (isEmailQuestion) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; isAnswered = emailRegex.test(String(answer).trim()); } else if (question.type === "birthplace") { const parsed = parseBirthplaceValue(answer); isAnswered = Boolean(parsed.country?.trim() && parsed.city?.trim()); } else if (question.type === "checkbox") { isAnswered = Array.isArray(answer) ? answer.length > 0 : hasAnswer; } } return (
); })}
); } export default function QuestionDetailClient({ closeLabel, continueLabel, description, informationLabel, itemSlug, locale = defaultLocale, questionsListHref, title, onClose, }: QuestionDetailClientProps) { const router = useRouter(); const { dictionary: t } = useI18n(); const [isTestStarted, setIsTestStarted] = useState(false); const [hasTestProgress, setHasTestProgress] = useState(false); const queryClient = useQueryClient(); const profileId = useCurrentProfileId(); const handleExit = useCallback(() => { if (onClose) { onClose(); return; } router.replace(questionsListHref); }, [onClose, questionsListHref, router]); // Hardware back in the detail page = navigate back to questions list. // QuestionAnswersProvider's pagehide/unmount safety net will flush // any pending answers automatically when the component unmounts. const handleHardwareBack = useCallback(async () => { handleExit(); return true; // handled — keep WebView open }, [handleExit]); useHardwareBackHandler(handleHardwareBack); useEffect(() => { if (typeof window !== "undefined" && profileId) { const draft = readScopedAssessmentDraft(profileId, itemSlug); if ( draft && typeof draft.answers === "object" && draft.answers !== null && Object.keys(draft.answers).length > 0 ) { setHasTestProgress(true); return; } setHasTestProgress(false); } }, [itemSlug, isTestStarted, profileId]); const isCattellSlug = itemSlug === "personality_test" || itemSlug === "cattell_test"; const isGlasserSlug = itemSlug === "glasser_5_needs_test" || itemSlug === "glasser_test"; const isAssessment = isCattellSlug || isGlasserSlug; const { data: overview, isLoading: isOverviewLoading, isError: isOverviewError, refetch: refetchOverview } = useFormOverviewQuery( "profile", locale, ); const { data: sectionResponse, isLoading: isSectionLoading, isError: isSectionError, refetch: refetchSection } = useFormSectionQuery( "profile", itemSlug, locale, Boolean(itemSlug) && !isAssessment, ); const items = useMemo( () => convertOverviewToFrontendItems(overview), [overview], ); const overviewItem = items.find((candidate) => candidate.slug === itemSlug); const item = useMemo(() => { if (!isAssessment && sectionResponse) { return mapBackendSectionToFrontend( sectionResponse.section, sectionResponse.section_progress.completion_percent, ); } return overviewItem; }, [isAssessment, overviewItem, sectionResponse]); const isSchemaLoading = isAssessment ? isOverviewLoading : !sectionResponse && (isOverviewLoading || isSectionLoading); const isSchemaError = isAssessment ? isOverviewError : (isOverviewError || isSectionError) && !sectionResponse; const cattellQuery = useCattellQuestionsQuery(locale, { enabled: isCattellSlug && isTestStarted, retry: 0, }); const submitCattellMutation = useSubmitCattellAssessmentMutation(); const glasserQuery = useGlasserQuestionsQuery(locale, { enabled: isGlasserSlug && isTestStarted, retry: 0, }); const submitGlasserMutation = useSubmitGlasserAssessmentMutation(); useEffect(() => { if (!isAssessment || isTestStarted) return; if (isCattellSlug) { void queryClient.prefetchQuery({ queryKey: marriageQueryKeys.cattellQuestions(locale), queryFn: () => getCattellQuestions(locale), staleTime: 30 * 1000, }); } else if (isGlasserSlug) { void queryClient.prefetchQuery({ queryKey: marriageQueryKeys.glasserQuestions(locale), queryFn: () => getGlasserQuestions(locale), staleTime: 30 * 1000, }); } }, [ isAssessment, isCattellSlug, isGlasserSlug, isTestStarted, locale, queryClient, ]); const cattellTestQuestions: TestQuestion[] = useMemo(() => { const questionsList = cattellQuery.data?.questions || []; const OPTION_KEYS = ["A", "B", "C"] as const; return questionsList .filter((q: any) => q && (q.question_number || q.id) && q.text) .map((q: any) => { const rawOptions = Array.isArray(q.options) ? q.options : []; const options = rawOptions.map((opt: any, idx: number) => { const key = OPTION_KEYS[idx] || String(idx); if (typeof opt === "string") { return { id: key, value: key, label: opt, }; } return { id: String(opt.id || opt.value || key), value: opt.value ?? key, label: String(opt.label || opt.text || opt.title || opt.name || key), }; }); return { id: Number(q.question_number || q.id), text: String(q.text), options, }; }); }, [cattellQuery.data]); const glasserTestQuestions: TestQuestion[] = useMemo(() => { const questionsList = glasserQuery.data?.questions || []; const DEFAULT_LABELS_FA = ["خیلی کم", "کم", "متوسط", "زیاد", "خیلی زیاد"]; const DEFAULT_LABELS_EN = [ "Very Low", "Low", "Moderate", "High", "Very High", ]; const defaultLabels = locale === "fa" ? DEFAULT_LABELS_FA : DEFAULT_LABELS_EN; return questionsList .filter((q: any) => q && (q.question_number || q.id) && q.text) .map((q: any) => { const rawOptions = Array.isArray(q.options) && q.options.length > 0 ? q.options : null; const options = rawOptions ? rawOptions.map((opt: any, idx: number) => { const score = idx + 1; if (typeof opt === "string") { return { id: String(score), value: score, label: opt }; } return { id: String(opt.id || opt.value || score), value: typeof opt.value === "number" ? opt.value : score, label: String( opt.label || opt.text || defaultLabels[idx] || String(score), ), }; }) : [1, 2, 3, 4, 5].map((score, idx) => ({ id: String(score), value: score, label: defaultLabels[idx] || String(score), })); return { id: Number(q.question_number || q.id), text: String(q.text), info: "factor" in q ? (q.factor as string) : "factor_code" in q ? (q.factor_code as string) : undefined, options, }; }); }, [glasserQuery.data, locale]); useEffect(() => { if (!isSchemaLoading && !isSchemaError && !item) { handleExit(); } }, [isSchemaLoading, isSchemaError, item, handleExit]); if (isSchemaLoading) { if (!isAssessment) { const loadingTitle = overviewItem?.title || title; return ( <>

{loadingTitle}

); } return ; } if (isSchemaError) { const errorTitle = overviewItem?.title || title; if (!isAssessment) { return ( <>

{errorTitle}

{ if (isOverviewError) refetchOverview(); if (isSectionError) refetchSection(); }} />
); } return ( <>

{errorTitle}

{ if (isOverviewError) refetchOverview(); }} />
); } if (!item) { return null; } if (item && item.questions.length === 0) { if (isTestStarted) { const isQuestionsLoading = isCattellSlug ? cattellQuery.isLoading : isGlasserSlug ? glasserQuery.isLoading : false; if (isQuestionsLoading) { return ; } const activeTestQuestions = isCattellSlug ? cattellTestQuestions : isGlasserSlug ? glasserTestQuestions : []; if (activeTestQuestions.length === 0) { const isError = isCattellSlug ? cattellQuery.isError : isGlasserSlug ? glasserQuery.isError : false; const refetch = isCattellSlug ? cattellQuery.refetch : glasserQuery.refetch; return ( <>

{isError ? locale === "fa" ? "ط®ط·ط§ ط¯ط± ط¯ط±غŒط§ظپطھ ط³ظˆط§ظ„ط§طھ ط§ط² ط³ط±ظˆط±. ظ„ط·ظپط§ظ‹ ط§ط² ط§طھطµط§ظ„ ط§غŒظ†طھط±ظ†طھ غŒط§ ظˆط±ظˆط¯ ط¨ظ‡ ط­ط³ط§ط¨ ع©ط§ط±ط¨ط±غŒ ط§ط·ظ…غŒظ†ط§ظ† ط­ط§طµظ„ ع©ظ†غŒط¯." : "Failed to load questions from server. Please check your connection or login status." : locale === "fa" ? "ط³ظˆط§ظ„ط§طھغŒ ط¨ط±ط§غŒ ط§غŒظ† ط¢ط²ظ…ظˆظ† غŒط§ظپطھ ظ†ط´ط¯." : "No questions found for this test."}

); } const handleTestFinish = async ( answers: Record, ) => { if (isCattellSlug) { const responses = Object.entries(answers).map(([qNum, option]) => ({ question_number: Number(qNum), option: String(option), })); await submitCattellMutation.mutateAsync({ responses }); try { const completionKey = getQuestionStorageKey(item.slug, profileId); if (completionKey) { window.localStorage.setItem( completionKey, JSON.stringify({ completed: true }), ); } } catch {} } else if (isGlasserSlug) { const responses = Object.entries(answers).map(([qNum, score]) => ({ question_number: Number(qNum), score: Number(score), })); await submitGlasserMutation.mutateAsync({ responses }); try { const completionKey = getQuestionStorageKey(item.slug, profileId); if (completionKey) { window.localStorage.setItem( completionKey, JSON.stringify({ completed: true }), ); } } catch {} } }; return ( setIsTestStarted(false)} onFinish={handleTestFinish} draftStorageKey={getTestDraftStorageKey(item.slug, profileId)} /> ); } const bulletKey = item.slug === "glasser_5_needs_test" ? "glasser" : "personality"; const bullets = bulletKey === "glasser" ? [ t[ "Understanding your five basic needs helps you recognize what truly drives your behavior in relationships." ], t[ "Taking this test is not mandatory, but it will help you better understand your priorities and find a more compatible spouse." ], t[ "By identifying your strongest needs, you can better communicate your expectations and build healthier relationships." ], ] : [ t[ "Do you struggle to understand the underlying psychological drivers that dictate your interpersonal connections and communication barriers?" ], t[ "Taking this test is not mandatory, but it will help you better search for a spouse. The Personality test is a test for self-knowledge and better understanding of your spouse." ], t[ 'Do you operate based on superficial behavioral adaptations, or are you aware of the deep "source traits" that fundamentally control your decision-making processes?' ], ]; return ( <>

{item.title}

{ setIsTestStarted(true); }} > {isCattellSlug ? (
    {[ { title: t["Understanding Personality Traits"], desc: t[ "Helps provide an overall picture of traits such as sociability, independence, emotional sensitivity, and interpersonal style." ], }, { title: t["Assessing Communication Style"], desc: t[ "Shows how a person typically communicates, expresses emotions, and builds closeness in relationships." ], }, { title: t["Understanding Responses to Stress and Conflict"], desc: t[ "Offers insight into emotional stability, tension levels, and how a person may react in difficult or stressful situations." ], }, { title: t["Assessing Independence and Decision-Making"], desc: t[ "Helps identify the person’s level of independence, assertiveness, and preference for individual or shared decision-making." ], }, { title: t["Identifying Potential Differences and Challenges"], desc: t[ "Comparing two individuals’ results can highlight personality differences that may require attention in a long-term relationship." ], }, { title: t["Supporting Better Match Recommendations"], desc: t[ "Alongside interviews and other relationship criteria, personality assessment can help make the matching process more targeted and improve the evaluation of compatibility." ], }, ].map((bullet, index) => (
  • {bullet.title}

    {bullet.desc}

  • ))}
) : isGlasserSlug ? (
    {[ { title: t["Understanding Core Psychological Needs"], desc: t[ "Helps identify the importance of the five basic needs—love and belonging, power, freedom, fun, and survival—in each person’s life." ], }, { title: t["Recognizing Relationship Expectations"], desc: t[ "Provides insight into what each person expects from a relationship, such as closeness, independence, security, achievement, or shared enjoyment." ], }, { title: t["Assessing Personality Compatibility"], desc: t[ "Helps compare personality traits, behavioral tendencies, and interaction styles to identify areas of compatibility between two individuals." ], }, { title: t["Identifying Potential Sources of Conflict"], desc: t[ "Differences in needs or personality styles can highlight areas where misunderstandings, tension, or disagreements may arise in the relationship." ], }, { title: t["Improving Mutual Understanding"], desc: t[ "Helps individuals better understand their own needs as well as their partner’s motivations, preferences, and emotional priorities." ], }, { title: t["Supporting More Suitable Match Recommendations"], desc: t[ "Combining needs and personality assessments with interviews and other marriage criteria can help make partner recommendations more personalized and well-matched." ], }, ].map((bullet, index) => (
  • {bullet.title}

    {bullet.desc}

  • ))}
) : null}
); } return ( <>

{item.title}

); }